package org.jflame.commons.crypto;

import org.jflame.commons.crypto.CipherArgs.SymmetricArgs;
import org.jflame.commons.crypto.Transformation.OpMode;
import org.jflame.commons.crypto.Transformation.Padding;
import org.jflame.commons.util.ArrayHelper;

import java.security.spec.AlgorithmParameterSpec;

/**
 * 对称加密,支持算法des,3des,aes,sm4,rc2,rc4
 * <p>
 * 字符串默认utf-8编码.<br>
 * <strong>AES说明:</strong>
 * <ol>
 * <li>AES的分组区块长度固定为128位(16byte)，密钥长度则可以是128，192或256位,jdk默认只支持128位密钥长度</li>
 * <li>支持填充方式ECB/CBC/PCBC/CTR/CTS/CFB/CFB8/CFB128/OFB/OFB8/OFB128,
 * NOPADDING/PKCS5PADDING/PKCS7Padding/ISO10126PADDING,<br>
 * 不设置填充模式jdk1.7默认AES/ECB/PKCS5Padding</li>
 * <li>使用PKCS7Padding实际是PKCS5Padding</li>
 * <li>使用NoPadding填充，请确保明文是16byte的倍数</li>
 * </ol>
 * <strong>DES/3DES说明:</strong>
 * <ol>
 * <li><strong>密钥24byte，向量IV 8byte(ECB无需向量)</strong></li>
 * <li>支持填充方式ECB/CBC/PCBC/CTR/CTS/CFB/CFB8/CFB64/OFB/OFB8/OFB64, NOPADDING/PKCS5PADDING/ISO10126PADDING,<br>
 * 不设置填充模式jdk1.7默认DESdede/ECB/PKCS5Padding</li>
 * <li>使用NoPadding填充，请确保明文是8的倍数</li>
 * </ol>
 * <strong>SM4说明:</strong>
 * <ol>
 * <li>SM4是国密对称加密算法,JCE未实现该算法,需要引入bcprov包</li>
 * <li>分组长度为128bit，密钥长度也是128bit</li>
 * </ol>
 * <strong>RC2/4说明:</strong>
 * <ol>
 * <li>RC4是一种流加密算法,无需分组和填充</li>
 * <li>RC4使用一个变长的密钥（通常为 8 至 256 字节)</li>
 * </ol>
 * <strong>PBE/PBES2说明:</strong>
 * <ol>
 * <li>PBE是消息摘要算法和对称加密算法的组合,基于用户易于记住的口令和随机盐,使用摘要算法计算出密钥,再使用对称加密,旧版JDK实现了基于MD5和DES,DESdede的加密,但算法已经不安全已经不推荐使用</li>
 * <li>PBES2使用新的消息摘要算法和对称加密算法的组合,jdk8已实现:HmacSHA1, HmacSHA224, HmacSHA256, HmacSHA384, or HmacSHA512和AES-CBC-PAD5
 * 的组合</li>
 * </ol>
 * 
 * @author zyc
 */
public class SymmetricEncryptor extends BaseEncryptor {

    /**
     * 构造函数,使用默认加密填充方式ECB/PKCS5Padding.
     */
    public SymmetricEncryptor() throws EncryptException {
        super(Transformation.AES_ECB_PKCS5PADDING);
    }

    /**
     * 构造函数
     * 
     * @param tf Transformation
     */
    public SymmetricEncryptor(final Transformation tf) throws EncryptException {
        this(tf, tf.getAlg() == Alg.SM4 ? "BC" : null);
    }

    /**
     * 构造函数,使用默认加密填充方式ECB/PKCS5Padding,同时指定加密提供器provider
     * 
     * @param tf Transformation
     * @param provider 加密提供器,如:BouncyCastleProvider
     */
    public SymmetricEncryptor(final Transformation tf, final String providerName) throws EncryptException {
        super(tf, providerName);
    }

    @Override
    public boolean isSupport() {
        return transformation.isSymmetric();
    }

    public SymmetricArgs buildArgs(String secretKeyStr) throws EncryptException {
        return new SymmetricArgs(transformation.getAlg(), secretKeyStr);
    }

    public SymmetricArgs buildArgs(String secretKeyStr, AlgorithmParameterSpec initParams) throws EncryptException {
        return new SymmetricArgs(transformation.getAlg(), secretKeyStr).initParam(initParams);
    }

    public SymmetricArgs buildArgs(byte[] rawKey, byte[] iv) throws EncryptException {
        SymmetricArgs paramSpec = new SymmetricArgs(transformation.getAlg(), rawKey);
        if (transformation.isNeedIV()) {
            if (ArrayHelper.isEmpty(iv)) {
                throw new EncryptException(transformation + "需要向量IV");
            } else {
                paramSpec.iv(iv);
            }
        }
        return paramSpec;
    }

    /**
     * aes加密字符串,加密方式AES/ECB/PKCS5PADDING
     * 
     * @param plainText 明文字符串
     * @param secretKey 密钥
     * @return 密文,base64编码字符串
     * @throws EncryptException
     */
    public static String aesEncrypt(String plainText, String secretKey) throws EncryptException {
        SymmetricEncryptor aes = new SymmetricEncryptor(Transformation.AES_ECB_PKCS5PADDING);
        return aes.encrypt(plainText, CipherArgs.buildSym(aes.getTransformation()
                .getAlg(), secretKey));
    }

    /**
     * 解密aes密文.
     * 
     * @param cipherBase64text AES/ECB/PKCS5PADDING加密base64编码的字符串
     * @param secretKey string密钥
     * @return 明文
     * @throws EncryptException
     */
    public static String aesDecrypt(String cipherBase64text, String secretKey) throws EncryptException {
        SymmetricEncryptor aes = new SymmetricEncryptor(Transformation.AES_ECB_PKCS5PADDING);
        return aes.decrypt(cipherBase64text, CipherArgs.buildSym(aes.getTransformation()
                .getAlg(), secretKey));
    }

    /**
     * 创建一个对称加密算法实例
     * 
     * @param alg 算法
     * @param mode 分组模式
     * @param padding 填充模式
     * @return
     * @throws EncryptException
     */
    public static SymmetricEncryptor of(Alg alg, OpMode mode, Padding padding) throws EncryptException {
        return new SymmetricEncryptor(new Transformation(alg, mode, padding));
    }

}
